#region using statements

using System;
using System.Collections.Generic;

using AeonOfStrife.Combat;
using AeonOfStrife.Misc;
using AeonOfStrife.Objects.Castle;
using AeonOfStrife.Objects.Units.UnitStates;

using AeonOfStrifeDatatypes.Combat;
using AeonOfStrifeDatatypes.Units.Combat;

using Microsoft.Xna.Framework;

using NGen;
using NGen.Managers.GameObject.Fsm;
using NGen.Systems.Alarms;

#endregion

namespace AeonOfStrife.Objects.Units
{
	public class Unit : GameObject2DFsm<Unit>, IAlarmListener<UnitAlarmType>, IAttacker, IAttackable
	{
		#region local fields

		protected internal readonly AlarmSystem<UnitAlarmType> m_alarmSystem = new AlarmSystem<UnitAlarmType>();

		protected internal readonly Dictionary<Type, Debuff> m_hsDebuffs = new Dictionary<Type, Debuff>();

		protected internal UnitStateAttack m_attackState;

		protected internal UnitStateDead m_deadState;

		protected internal UnitStateIdle m_idleState;

		protected internal UnitStateRun m_runState;

		protected internal UnitStateStun m_stunState;

		public TeamType Team { get; set; }

		public int HpCurrent { get; protected internal set; }

		public int MoveSpeedCurrent { get; protected internal set; }

		public UnitStats SharedStats { get; protected internal set; }

		public EnemyPriority EnemyPriority { get; set; }

		#endregion

		#region local properties

		public new UnitState CurrentState { get { return base.CurrentState as UnitState; } }

		#endregion

		#region constructor

		internal Unit(UnitStats p_stats) {
			SharedStats = p_stats;
			HpCurrent = SharedStats.m_hpMax;
			MoveSpeedCurrent = SharedStats.m_moveSpeedMax;
			m_idleState = new UnitStateIdle(p_stats.m_idleAnimation);
			m_runState = new UnitStateRun(p_stats.m_runAnimation);
			m_attackState = new UnitStateAttack(p_stats.m_lAttacks.ToArray(), p_stats.m_idleAnimation);
			m_stunState = new UnitStateStun(p_stats.m_idleAnimation);
			m_deadState = new UnitStateDead(p_stats.m_idleAnimation);
			m_zDepth =  0.9f - m_position.Y / GameEngine.GameWindow.ClientBounds.Y;
		}

		#endregion

		#region GameObject implementation

		protected override void _Initialize() {
			Change(m_idleState);
		}

		protected override void _Update(GameTime p_gameTime) {
			if(HpCurrent <= 0)
				return;
			m_zDepth = 0.1f +  (m_position.Y / GameEngine.GameWindow.ClientBounds.Height) / 10f;
			m_scale.X =  (m_position.Y * 2f / GameEngine.GameWindow.ClientBounds.Height);
			m_scale.Y =  (m_position.Y * 2f / GameEngine.GameWindow.ClientBounds.Height);
			m_origin.X = CurrentState.AnimatedSprite.AnimationFrameSize.X / 2f;
			m_origin.Y = CurrentState.AnimatedSprite.AnimationFrameSize.Y / 2f;
			CurrentState.Update(p_gameTime);
			m_alarmSystem.Update(p_gameTime);
			ProcessDebuffs(p_gameTime);
		}

		protected override void _Draw(GameTime p_gameTime) {
			CurrentState.Draw(p_gameTime);
		}

		protected override void _Cleanup() {
			while(Pop()) {
				;
			}
			m_hsDebuffs.Clear();
			GameEngine.GetService<IUnitManager>().RemoveUnit(this);
			if(ThrowDeathEvent != null) {
				ThrowDeathEvent(this);
			}

			var pathData = GameEngine.GetService<PathfindingData>();
			var myCell = pathData.ConvertScreenToCellCoord(m_position);
			m_runState.UpdatePathfindingData();
			pathData.DecrementMultiplier(myCell.X, myCell.Y);
		}

		#endregion

		#region local methods

		public void ApplyDebuff(Debuff p_debuff) {
			if(m_hsDebuffs.ContainsKey(p_debuff.GetType())) {
				m_hsDebuffs[p_debuff.GetType()].OnReApply(p_debuff);
				return;
			}

			p_debuff.OnApply();
		}

		internal void ProcessDebuffs(GameTime p_gameTime) {
			foreach(var key in m_hsDebuffs.Keys) {
				if(m_hsDebuffs[key].Update(p_gameTime)) {
					m_hsDebuffs.Remove(key);
				}
			}
		}

		internal void AddAlarm(AlarmObject<UnitAlarmType> p_alarm) {
			p_alarm.AlarmEvent += OnAlarmEvent;
			m_alarmSystem.Add(p_alarm);
		}

		internal void RemoveAlarm(AlarmObject<UnitAlarmType> p_alarm) {}

		internal bool InRangeOfTarget() {
			if(Target == null) {
				return false;
			}
			if(Math.Abs(m_position.Y - Target.Position.Y) > SharedStats.m_attackRange / 2) {
				return false;
			}
			return Vector2.DistanceSquared(m_position, Target.Position) < Math.Pow(SharedStats.m_attackRange, 2);
		}

		#endregion

		#region IAlarmListener implementation

		public void OnAlarmEvent(UnitAlarmType p_message) {
			switch(p_message) {
				case UnitAlarmType.ATTACK_ALARM:
					m_attackState.Attack();
					break;
				case UnitAlarmType.CHANGE_TARGET:
					
					AcquireTarget();
					Change(m_runState);
					break;
			}
		}

		#endregion

		#region IAttacker implementation

		public IAttackable Target { get; protected internal set; }

		public event UnitAttackEvent ThrowAttackEvent;

		public void OnTargetDeath(IAttackable p_attackee) {
			ThrowAttackEvent -= p_attackee.OnAttacked;
			Change(m_idleState);
		}

		public void AcquireTarget() {
			if(Target != null) {
				Target.UnregisterAttacker(this);
				ThrowAttackEvent -= Target.OnAttacked;
			}
			
			Target = GameEngine.GetService<IUnitManager>().GetTarget(Team);
			
			if(Target != null) {
				Target.RegisterAttacker(this);
				ThrowAttackEvent += Target.OnAttacked;
			}
		}

		public void AttackTarget(DamageValues p_damage) {
			if(ThrowAttackEvent != null) {
				ThrowAttackEvent(this, p_damage);
			}
		}

		#endregion

		#region IAttackable implementation

		public event UnitDeathEvent ThrowDeathEvent;

		public int Hp {
			get { return HpCurrent; }
		}

		public Vector2 Position { get { return m_position; } }

		public void OnAttacked(IAttacker p_attacker, DamageValues p_damage) {
			if(GameEngine.Random.Next(100) < SharedStats.m_dodgeAttackRate) {
				return;
				//TODO dodge graphic
			}

			if(GameEngine.Random.Next(100) < SharedStats.m_blockAttackRate) {
				return;
				//TODO dodge graphic
			}

			if(GameEngine.Random.Next(100) < SharedStats.m_parryAttackRate) {
				return;
				//TODO parry graphic
			}

			HpCurrent -= DamageValues.GetResolvedCombatDamage(Defense, p_damage);
			
			if(HpCurrent <= 0) {
				Destroy(Id);
			}
		}

		public DamageValues Defense {
			get { return DamageValues.Add(SharedStats.m_baseDefense, SharedStats.m_bonusDefense); }
		}

		public void RegisterAttacker(IAttacker p_attacker) {
			ThrowDeathEvent += p_attacker.OnTargetDeath;
		}

		public void UnregisterAttacker(IAttacker p_attacker) {
			ThrowDeathEvent -= p_attacker.OnTargetDeath;
		}

		#endregion
	}
}